package com.example.boot.security.sms;

/**
 * TODO
 *
 * @author ding
 * @since 2024/7/24
 */

import com.example.mapper.AdminMapper;
import com.example.pojo.entity.*;
import com.example.pojo.utils.LoginUser;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;


/**
 * @description: 短信登录身份认证组件
 **/
@Slf4j
@Component
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private AdminMapper adminMapper;


    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String mobile = (String) authentication.getPrincipal();
        //根据手机号加载用户
        UserDetails user = loadUserByPhone(mobile);

        Object principalToReturn = user;
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

    @Override
    public boolean supports(Class<?> aClass) {
        //如果是SmsCodeAuthenticationToken该类型，则在该处理器做登录校验
        return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
    }


    /**
     * 跟账号登录保持一致
     * @param principal
     * @param authentication
     * @param user
     * @return
     */
    protected Authentication createSuccessAuthentication(Object principal,
                                                         Authentication authentication, UserDetails user) {
        // Ensure we return the original credentials the user supplied,
        // so subsequent attempts are successful even with encoded passwords.
        // Also ensure we return the original getDetails(), so that future
        // authentication events after cache expiry contain the details
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                principal, authentication.getCredentials(),
                authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());

        return result;
    }

    /**
     * 获取用户信息
     * @param phone
     * @return
     * @throws UsernameNotFoundException
     */
    public UserDetails loadUserByPhone(String phone) throws UsernameNotFoundException {
        MPJLambdaWrapper<Admin> mpjLambdaWrapper = new MPJLambdaWrapper<>();
        mpjLambdaWrapper
                .selectAll(Admin.class)
                .selectCollection(Role.class, Admin::getRoles, set -> set.collection(Permission.class, Role::getPermissions))
                .innerJoin(UserAndRole.class, UserAndRole::getUserId, Admin::getId)
                .innerJoin(Role.class, Role::getId, UserAndRole::getRoleId)
                .innerJoin(RoleAndPermission.class, RoleAndPermission::getRoleId, Role::getId)
                .innerJoin(Permission.class, Permission::getId, RoleAndPermission::getPermissionId)
                .eq(StringUtils.hasLength(phone), Admin::getPhone, phone);
        Admin admin = adminMapper.selectJoinOne(Admin.class, mpjLambdaWrapper);
        if (admin == null) {
            throw new UsernameNotFoundException("该手机号不存在");
        }

        //封装LoginUser对象 包含账户名 密码 是否可用
        LoginUser loginUser = new LoginUser(admin);

        //将Authentication对象（用户信息，已认证转态、权限信息） 存入 SecurityContextConfig
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        return loginUser;
    }
}
